from typing import Dict, List, Tuple
from datetime import datetime

from ctypes import *
lib = CDLL("PushkinDll.dll")

from pts.object import BarData
from .base import to_int
from pts.setting import CHANSETTINGS, CHANSETTING_FILENAME


# 定义缠论配置选项结构体
class ChanConfig(Structure):
    _fields_ = [
        ('ChanKind', c_int),            # 缠论类型。0 - 线中枢、1 - 笔中枢 - 1、2 - 笔中枢维持 - 2、3 - 笔中枢 - 3、4 - 笔中枢维持 - 4
        ('TypeBaoHan', c_int),          # 包含关系的处理方法。1 - 经典理论法；2 - 逐K线分析法
        ('TypeBi1', c_int),             # 笔划分：划分前处理包含关系。1 - 处理；0 - 不处理
        ('TypeBi2', c_int),             # 笔划分：进行笔的修正。1 - 修正；0 - 不修正
        ('TypeBi3', c_int),             # 笔划分：允许次高(低)点成笔。1 - 允许；0 - 不允许
        ('TypeBi4', c_int),             # 笔划分：允许缺口成笔。1 - 允许；0 - 不允许
        ('TypeBi5', c_int),             # 笔划分：允许顶底K线重叠。1 - 允许；0 - 不允许
        ('TypeQuekou', c_int),          # 允许带缺口的笔升为线段。1 - 允许；0 - 不允许
        ('TypeBiaoZhunHua', c_int),     # 对结果进行标准化处理。1 - 处理；0 - 不处理
        ('TypeZhongBreak', c_int)       # 中枢划分方法。1 - 限制延伸，9段中枢结束；2 - 不限制延伸，中枢可超过9段
        ]


# 定义缠论笔节点结构体
class ChanPotBi(Structure):
    _fields_ = [
        ('fenxing', c_byte),    # 分型标记。1：顶；-1：底；0：非顶底（非顶底时其它域没有意义）

        ('zhshg', c_float),     # 中枢高 / 低
        ('zhshd', c_float),
        ('zhshgg', c_float),    # 中枢最高 / 最低
        ('zhshdd', c_float),
        ('zhsh', c_byte),       # 中枢首尾标记。中枢开始最低位为1；中枢结束次低位为1

        ('beichi', c_byte)      # 笔背驰标记。取值范围：-9~9。取值含义见《普吸金缠论大全》
        ]


# 定义缠论线段节点结构体
class ChanPotXian(Structure):
    _fields_ = [
        ('fenxing', c_byte),    # 分型标记。1：顶；-1：底；0：非顶底（非顶底时其它域没有意义）
        ('xian', c_byte),       # 线段标记。取值与fenxing相同

        ('zhshg', c_float),     # 中枢高 / 低
        ('zhshd', c_float),
        ('zhshgg', c_float),    # 中枢最高 / 最低
        ('zhshdd', c_float),
        ('zhsh', c_byte),       # 中枢首尾标记。中枢开始最低位为1；中枢结束次低位为1

        ('xianmm1', c_byte),    # 买卖点标记：一类。-1是买点，1是卖点，0不是买卖点
        ('xianmm2', c_byte),    # 买卖点标记：二三
        ('xianmm3', c_byte),    # 买卖点标记：三类
        ('xianmp1', c_byte),    # 买卖点标记：盘一
        ('xianmp2', c_byte),    # 买卖点标记：盘二
        ('xianml2', c_byte),    # 买卖点标记：类二
        ('xianpu2', c_byte),    # 买卖点标记：普二
        ('beichi', c_byte)      # 线段背驰标记。取值范围：-9~9。取值含义见《普吸金缠论大全》
        ]
# 缠论买卖点在线段节点上标注。-1表示买点，1表示卖点，0表示无买卖点。
# 背驰是一个数值，0表示无背驰，<0表示底背驰，>0表示顶背驰，绝对值越大，表示背驰越明显。


# 定义缠论分析结果结构体
# 返回结果主要是对当下买卖点的描述
class ChanResult(Structure):
    _fields_ = [
        # 经典缠论买卖点在线段节点中标记，以下两域是对当下经典缠论买卖点的描述
        ('m_Signal0', c_int),  # 缠论买卖点信号：0-无买/卖点；1-买卖点结构形成；2-分型；3-分型之后；4、5-分型停顿；6-停顿之后
        ('m_Mark0', c_float),  # 缠论买点得分。买点为负，卖点为正，绝对值越大越好

        ('m_MarkDay', c_float),  # 当前的日线K线得分

        # 以下是对三类扩展买点的描述
        ('xiankz1', c_int),     # 扩展买点标记（只有买点，没有卖点）。-1：买点；0：无买点
        ('xiankz2', c_int),
        ('xiankz3', c_int),
        ('m_Signal1', c_int),  # 扩展买点1信号
        ('m_Signal2', c_int),  # 扩展买点2信号
        ('m_Signal3', c_int),  # 扩展买点3信号
        ('m_Mark1', c_float),  # 扩展买点1得分。买点值为负，绝对值越大越好
        ('m_Mark2', c_float),  # 扩展买点2得分
        ('m_Mark3', c_float),  # 扩展买点3得分
    ]
# 扩展买点为定制买点，不同买点的信号值定义各不相同。总得来说，越发展，值越大。


class BarManager:
    """K线管理器"""

    def __init__(self):
        """初始化"""
        # K线字典：键-日期时间；值-K线数据
        self._bars: Dict[datetime, BarData] = {}
        # 日期时间-索引字典
        self._datetime_index_map: Dict[datetime, int] = {}
        # 索引-日期时间字典
        self._index_datetime_map: Dict[int, datetime] = {}

        # 数据范围缓存
        # 价格范围字典
        self._price_ranges: Dict[Tuple[int, int], Tuple[float, float]] = {}
        # 成交量字典
        self._volume_ranges: Dict[Tuple[int, int], Tuple[float, float]] = {}

        # 用于缠论分析的动态数组
        self.nums = 0
        self.inOpen = None
        self.inHigh = None
        self.inLow = None
        self.inClose = None
        self.inVolume = None
        self.outBi = None
        self.outXian = None
        self.outResult = ChanResult()


    def update_history(self, history: List[BarData]) -> None:
        """
        Update a list of bar data.
        接受一个K线数据列表
        """
        # Put all new bars into dict
        # 将所有K线放入K线字典
        for bar in history:
            self._bars[bar.datetime] = bar

        # Sort bars dict according to bar.datetime
        # 将字典按照日期时间排序
        self._bars = dict(sorted(self._bars.items(), key=lambda tp: tp[0]))

        # Update map relationiship
        # 更新日期时间与索引的关系字典
        ix_list = range(len(self._bars))
        dt_list = self._bars.keys()

        self._datetime_index_map = dict(zip(dt_list, ix_list))
        self._index_datetime_map = dict(zip(ix_list, dt_list))

        # Clear data range cache
        # 清空数据范围缓存
        self._clear_cache()

        # 重新计算缠论分析结果
        self.recalc_chan()

    def update_bar(self, bar: BarData) -> None:
        """
        Update one single bar data.
        接受一个单独的K线数据
        """
        # 取得日期时间
        dt = bar.datetime

        # 如果日期时间不在关系字典中，关系字典中增加项
        if dt not in self._datetime_index_map:
            ix = len(self._bars)
            self._datetime_index_map[dt] = ix
            self._index_datetime_map[ix] = dt

        # 更新K线字典
        self._bars[dt] = bar

        # 清空数据范围缓存
        self._clear_cache()

        # 重新计算缠论分析结果
        self.recalc_chan()

    def recalc_chan(self):
        """
        重新计算缠论分析结果
        """
        # 取普吸金缠论的版本号
        ver = lib.get_version()
        print("普吸金缠论的版本号: ", ver)

        # 配置普吸金缠论选项
        self.chan_config()

        # 释放原有空间
        if self.nums > 0:
            del self.inOpen
            del self.inHigh
            del self.inLow
            del self.inClose
            del self.inVolume
            del self.outBi
            del self.outXian
            self.nums = 0

        # 取所有K线数据
        bars: List[BarData] = list(self._bars.values())

        # 申请空间并初始化
        self.nums = len(bars)
        if self.nums <= 0:
            return
        self.inOpen = (c_float * self.nums)()
        self.inHigh = (c_float * self.nums)()
        self.inLow = (c_float * self.nums)()
        self.inClose = (c_float * self.nums)()
        self.inVolume = (c_float * self.nums)()
        self.outBi = (ChanPotBi * self.nums)()
        self.outXian = (ChanPotXian * self.nums)()
        for i in range(self.nums):
            bar = bars[i]
            self.inOpen[i] = bar.open_price
            self.inHigh[i] = bar.high_price
            self.inLow[i] = bar.low_price
            self.inClose[i] = bar.close_price
            self.inVolume[i] = bar.volume
            # 返回数组不需要初始化，在DLL中初始化

        errNo = c_int(0)  # 不能省略
        lib.pushkin_chan.argtypes = [c_int, POINTER(c_float), POINTER(c_float), POINTER(c_float), POINTER(c_float),
                                     POINTER(c_float), c_void_p, c_void_p, POINTER(ChanResult), POINTER(c_int)]
        lib.pushkin_chan(self.nums, self.inOpen, self.inHigh, self.inLow, self.inClose,
                         self.inVolume, self.outBi, self.outXian, self.outResult, errNo)

        # 检验结果
        #for i in range(self.nums):
        #    if self.outBi[i].fenxing != 0:
        #        # print(i, end=" ")
        #        if self.outBi[i].zhsh != 0:
        #            print("中枢： ")
        #            print(self.outBi[i].zhsh, end=" ")
        #            print(self.outBi[i].zhshg, end=" ")
        #            print(self.outBi[i].zhshd, end=" ")
        #            print(self.outBi[i].zhshgg, end=" ")
        #            print(self.outBi[i].zhshdd, end=" ")
        #        if self.outBi[i].zhsh != 0:
        #            print("背驰： ")
        #            print(self.outBi[i].zhsh, end=" ")

        for i in range(self.nums):
            if self.outXian[i].fenxing != 0:
                print(i, end=" ")
                if self.outXian[i].xianmp1 !=0:
                    print("盘一", self.outXian[i].xianmp1, end=" ")
        print("\n")

        # errNo：错误码。0 - 无错误；98 - 数据量太少；99 - 软件已过期
        print("缠论分析返回值：", errNo.value)

        print("显示买点结果：")
        print("m_Signal0：", self.outResult.m_Signal0)
        print("m_Mark0：", self.outResult.m_Mark0)
        print("m_MarkDay：", self.outResult.m_MarkDay)
        print("xiankz1：", self.outResult.xiankz1)
        print("xiankz2：", self.outResult.xiankz2)
        print("xiankz3：", self.outResult.xiankz3)
        print("m_Signal1：", self.outResult.m_Signal1)
        print("m_Signal2：", self.outResult.m_Signal2)
        print("m_Signal3：", self.outResult.m_Signal3)
        print("m_Mark1：", self.outResult.m_Mark1)
        print("m_Mark2：", self.outResult.m_Mark2)
        print("m_Mark3：", self.outResult.m_Mark3)

    def chan_config(self):
        cc = ChanConfig()
        cc.ChanKind = CHANSETTINGS["ChanKind"]
        cc.TypeBaoHan = CHANSETTINGS["TypeBaoHan"]
        cc.TypeBi1 = CHANSETTINGS["TypeBi1"]
        cc.TypeBi2 = CHANSETTINGS["TypeBi2"]
        cc.TypeBi3 = CHANSETTINGS["TypeBi3"]
        cc.TypeBi4 = CHANSETTINGS["TypeBi4"]
        cc.TypeBi5 = CHANSETTINGS["TypeBi5"]
        cc.TypeQuekou = CHANSETTINGS["TypeQuekou"]
        cc.TypeBiaoZhunHua = CHANSETTINGS["TypeBiaoZhunHua"]
        cc.TypeZhongBreak = CHANSETTINGS["TypeZhongBreak"]
        # 配置成功后，返回所设置的缠论类型
        chan_kind = lib.set_config(cc)

        print("普吸金缠论配置成功: ", chan_kind)

    def get_count(self) -> int:
        """
        Get total number of bars.
        取得K线数
        """
        return len(self._bars)

    def get_index(self, dt: datetime) -> int:
        """
        Get index with datetime.
        根据日期时间取得索引
        """
        return self._datetime_index_map.get(dt, None)

    def get_datetime(self, ix: float) -> datetime:
        """
        Get datetime with index.
        根据索引取得日期时间
        注：本函数参数为浮点型，应该是从图上取的位置坐标
        """
        ix = to_int(ix)
        return self._index_datetime_map.get(ix, None)

    def get_bar(self, ix: float) -> BarData:
        """
        Get bar data with index.
        根据索引取得K线数据
        """
        ix = to_int(ix)
        dt = self._index_datetime_map.get(ix, None)
        if not dt:
            return None

        return self._bars[dt]

    def get_all_bars(self) -> List[BarData]:
        """
        Get all bar data.
        取得所有的K线数据，以列表形式返回
        """
        return list(self._bars.values())

    def get_price_range(self, min_ix: float = None, max_ix: float = None) -> Tuple[float, float]:
        """
        Get price range to show within given index range.
        取得指定索引范围内的价格取值范围
        """
        if not self._bars:
            return 0, 1

        if not min_ix:
            min_ix = 0
            max_ix = len(self._bars) - 1
        else:
            min_ix = to_int(min_ix)
            max_ix = to_int(max_ix)
            max_ix = min(max_ix, self.get_count())

        buf = self._price_ranges.get((min_ix, max_ix), None)
        if buf:
            return buf

        bar_list = list(self._bars.values())[min_ix:max_ix + 1]
        first_bar = bar_list[0]
        max_price = first_bar.high_price
        min_price = first_bar.low_price

        for bar in bar_list[1:]:
            max_price = max(max_price, bar.high_price)
            min_price = min(min_price, bar.low_price)

        self._price_ranges[(min_ix, max_ix)] = (min_price, max_price)
        return min_price, max_price

    def get_volume_range(self, min_ix: float = None, max_ix: float = None) -> Tuple[float, float]:
        """
        Get volume range to show within given index range.
        取得指定索引范围内的成交量取值范围
        """
        if not self._bars:
            return 0, 1

        if not min_ix:
            min_ix = 0
            max_ix = len(self._bars) - 1
        else:
            min_ix = to_int(min_ix)
            max_ix = to_int(max_ix)
            max_ix = min(max_ix, self.get_count())

        buf = self._volume_ranges.get((min_ix, max_ix), None)
        if buf:
            return buf

        bar_list = list(self._bars.values())[min_ix:max_ix + 1]

        first_bar = bar_list[0]
        max_volume = first_bar.volume
        min_volume = 0

        for bar in bar_list[1:]:
            max_volume = max(max_volume, bar.volume)

        self._volume_ranges[(min_ix, max_ix)] = (min_volume, max_volume)
        return min_volume, max_volume

    def _clear_cache(self) -> None:
        """
        Clear cached range data.
        清空数据范围缓存
        """
        self._price_ranges.clear()
        self._volume_ranges.clear()

    def clear_all(self) -> None:
        """
        Clear all data in manager.
        清空本管理器的所有数据
        """
        self._bars.clear()
        self._datetime_index_map.clear()
        self._index_datetime_map.clear()

        self._clear_cache()
